/**
 *
 *
 * NAME:
 *   digenv - print all or part of environment as a sorted list
 *
 * SYNTAX:
 *   digenv [VARIABLE ...]
 *
 * DESCRIPTION
 *  Prints the values of all environment variables as a sorted list. If one or
 *  more arguments are specified only the environment variables that contain one
 *  or more of these arguments in its name or value.
 *
 * ENVIRONMENT
 *  PAGER
 *    This variable specifies the default pager to be used.
 *
 * SEE ALSO
 *   printenv(1), sort(1), grep(1)
 *
 * AUTHOR
 *   Helen Elemida elemida@kth.se
 *   David Falk dfa@kth.se
 */

#include <sys/types.h> /* defines the type pid_t */
#include <sys/wait.h>
#include <errno.h> /* definierar bland annat perror() */
#include <stdio.h> /* definierar bland annat stderr */
#include <stdlib.h> /* definierar bland annat rand() och RAND_MAX */
#include <unistd.h> /* definierar bland annat pipe() */

pid_t child_pid; /* för child-processens PID vid fork() */

int main(int argc, char *argv[], char *envp[])
{
	int return_value; /* för returvärden från systemanrop */
	int pipe_printenv[2]; /* PIPE ska kommenteras */
	int pipe_sort[2]; /* PIPE ska kommenteras */
	int pipe_grep[2]; /* PIPE ska kommenteras */
	char *execArgs[2];
	char * pager;
	int status = -1;

	execArgs[1] = NULL;

	return_value = pipe( pipe_printenv ); /* skapa en pipe */
	if( -1 == return_value ) /* avsluta programmet om pipe() misslyckas */
	{ perror( "Cannot create pipe" ); exit( 1 ); }

	return_value = pipe( pipe_sort ); /* skapa en pipe */
	if( -1 == return_value ) /* avsluta programmet om pipe() misslyckas */
	{ perror( "Cannot create pipe" ); exit( 1 ); }

	return_value = pipe( pipe_grep ); /* skapa en pipe */
	if( -1 == return_value ) /* avsluta programmet om pipe() misslyckas */
	{ perror( "Cannot create pipe" ); exit( 1 ); }

	child_pid = fork(); /* skapa en child-process */
	if( 0 == child_pid )
	{

		/* duplicate pipe to stdout and close stdout */
		return_value = dup2(pipe_printenv[1],1);
		if( -1 == return_value )
		{ perror( "Cannot duplicate pipe" ); exit( 1 ); }


		/* close read side of pipe -- printenv should only write to pipe */
		return_value = close( pipe_printenv[ 0 ] );
		if( -1 == return_value )
		{ perror( "Cannot close pipe" ); exit( 1 ); }

		execArgs[0] = "printenv";
		/* Unneccesary to check return value, execvp does not return
		 * unless an error has occured */
		return_value = execvp(execArgs[0], execArgs);
		fprintf(stderr, "Error with exec\n ");
		exit(1);
	}else
	{
		if(child_pid==-1){
			perror( "Error with forking process, no child process created" );
			exit(1);
		}

		waitpid(child_pid,&status,0);
		if (WIFEXITED(status)) {
			if(0 < WEXITSTATUS(status)){
				fprintf(stderr, "Error with accessing environment variables \n");
				exit(2);
			}
		}



		/* close write side of pipe -- only child above should write to pipe */
		return_value = close( pipe_printenv[ 1 ] );
		if( -1 == return_value )
		{ perror( "Cannot close pipe" ); exit( 1 ); }

		if(argc>1){
			child_pid = fork(); /* skapa en child-process */
			if( 0 == child_pid )
			{
				char * args[argc+1];

				/* duplicate pipe printenv to stdin and close stdin */
				return_value = dup2(pipe_printenv[0],0);
				if( -1 == return_value )
				{ perror( "Cannot duplicate pipe" ); exit( 1 ); }

				/* duplicate pipe pipe_grep to stdout and close stdout */
				return_value = dup2(pipe_grep[1],1);
				if( -1 == return_value )
				{ perror( "Cannot duplicate pipe" ); exit( 1 ); }

				/* closing stderr */
				return_value = close(2);
				if( -1 == return_value )
				{ perror( "Cannot close stderr" ); exit( 1 ); }

				int i = 1;
				while(i<argc){
					args[i] = argv[i];
					i++;
				}

				args[0] = "grep";
				args[i] = NULL;
				/* Unneccesary to check return value, execvp does not return
				 * unless an error has occured */
				return_value = execvp(args[0], args);
				fprintf(stderr, "Error with exec\n ");
				exit(1);
			}
			if(child_pid==-1){
				perror( "Error with forking process, no child process created");
				exit(1);
			}
			waitpid(child_pid,&status,0);
			if (WIFEXITED(status)) {
				if(2 == WEXITSTATUS(status)){
					fprintf(stderr, "Syntax error in input or cannot access input file \n");
					exit(3);
				}
			}
		} else {
			/* If grep is not run then reading pipe_grep should be the same as
			 * if reading pipe_printenv.
			 * duplicate pipe pipe_grep to pipe_printenv and close pipe_printenv*/
			return_value = dup2(pipe_printenv[0],pipe_grep[0]);
			if( -1 == return_value )
			{ perror( "Cannot duplicate pipe" ); exit( 1 ); }
		}

		/* close write side of pipe -- only child above should write to pipe */
		return_value = close(pipe_grep[1]);
		if( -1 == return_value )
		{ perror( "Cannot close pipe" ); exit( 1 ); }

		child_pid = fork(); /* skapa en child-process */
		if( 0 == child_pid )
		{

			/* duplicate pipe_grep to stdin and close stdin */
			return_value = dup2(pipe_grep[0],0);
			if( -1 == return_value )
			{ perror( "Cannot duplicate pipe" ); exit( 1 ); }

			/* duplicate pipe_sort to stdout and close stdout */
			return_value = dup2(pipe_sort[1],1);
			if( -1 == return_value )
			{ perror( "Cannot duplicate pipe" ); exit( 1 ); }

			execArgs[0] = "sort";
			/* Unneccesary to check return value, execvp does not return
			 * unless an error has occured */
			return_value = execvp(execArgs[0], execArgs);
			fprintf(stderr, "Error with exec\n");
			exit(1);
		}else
		{
			if(child_pid==-1){
				perror( "Error with forking process, no child process created");
				exit(1);
			}
			waitpid(child_pid,&status,0);
			if (WIFEXITED(status)) {
				if(0 < WEXITSTATUS(status)){
					fprintf(stderr, "Error with sorting variables \n");
					exit(4);
				}
			}
		}

		/* duplicate pipe_grep to stdin and close stdin */
		return_value = dup2(pipe_sort[0],0);
		if( -1 == return_value )
		{ perror( "Cannot duplicate pipe" ); exit( 1 ); }

		/* close write side of pipe -- only child above should write to pipe */
		return_value = close(pipe_sort[1]);
		if( -1 == return_value )
		{ perror( "Cannot close pipe" ); exit( 1 ); }

		// Get environment variable PAGER
		pager = getenv("PAGER");

		// Only use pager if it is not null
		if(pager != NULL){
			execArgs[0] = pager;
			return_value = execvp(execArgs[0], execArgs);
		}

		// PAGER was null, trying less
		execArgs[0] = "less";
		return_value = execvp(execArgs[0], execArgs);
		// less did not exist, trying more
		execArgs[0] = "more";
		/* Unneccesary to check return value, execvp does not return
		 * unless an error has occured */
		return_value = execvp(execArgs[0], execArgs);

		fprintf(stderr, "Error with exec\n ");
		exit(1);
	}
	return 0;
}
